package edu.vt.ece.rt.benchmark.bank;

import edu.vt.ece.rt.dstm.Context;
import edu.vt.ece.rt.dstm.ContextDelegator;
import edu.vt.ece.rt.dstm.QObject;
import edu.vt.ece.rt.dstm.TMProxy;
import edu.vt.ece.rt.quorum.ClustersManager;
import edu.vt.ece.rt.util.Logger.Logger;

/**
 * @author Sudhanshu
 */

public class BankAccount extends QObject {

	private Integer amount = 0;
	private String id;

	public BankAccount(String ID) {
		id = ID;
		TMProxy.getTMProxy().publishObject(this);
	}
	
	public BankAccount(){}		//For cloning
	/**To create Account with some money at start
	 * @param id
	 * @param amount2
	 */
	public BankAccount(String ID, int amount2) {
		id = ID;		
		amount = new Integer(amount2);		
		TMProxy.getTMProxy().publishObject(this);
	}

	public String getId() {
		return id;
	}

	public void deposit(int dollars) {
		amount = amount + dollars;
	}

	public boolean withdraw(int dollars) {
		amount = amount - dollars;
		return amount >= 0;
	}

	/**
	 * Provides the balance of given account
	 * 
	 * @return
	 */
	public long checkBalance() {
		return amount;
	}

	public static long totalBalance(String accountNum1, String accountNum2,
			Context c) throws Exception {

		BankAccount account1 = (BankAccount) TMProxy.getTMProxy().read(
				accountNum1, c.getContextId());
		BankAccount account2 = (BankAccount) TMProxy.getTMProxy().read(
				accountNum2, c.getContextId());

		long balance = 0;
		for (int i = 0; i < Benchmark.calls; i++)
			balance += account1.checkBalance();

		for (int i = 0; i < Benchmark.calls; i++)
			balance += account2.checkBalance();

		return balance;

	}

	/**
	 * Get the total balance in given to accounts
	 * 
	 * @param accountNum1
	 *            - Id of bank account number 1
	 * @param accountNum2
	 *            - Id of bank account number 2
	 */
	public static long totalBalance(String accountNum1, String accountNum2) {
		long result = 0;
		for (int i = 0; i < 0x7fffffff; i++) {
			Context context = ContextDelegator.getContext();
			context.init(1);

			Logger.debug("----Started----- R Transaction "+context.getContextId()+" on "+ClustersManager.baseClusterId);
			try {
				result = totalBalance(accountNum1, accountNum2, context);
			} catch (Exception e) {
				e.printStackTrace();
			}

			try {
				if (TMProxy.getTMProxy().requestCommit(context.getContextId())) {
					return result;
				}
			} catch (Exception e) {
				e.printStackTrace();
			}
		}
		Logger.fetal("Unable to complete transaction in given number of tries");
		return result;
	}

	/**
	 * Transfers the given amount of money between different accounts
	 * 
	 * @param id1
	 *            - Bank Account Number 1 ID
	 * @param id2
	 *            - Bank Account Number 2 ID
	 * @param transfer
	 *            - Amount to be transferred
	 */
	public static void transfer(String accountNum1, String accountNum2,
			int transfer) {

		for (int i = 0; i < 0x7fffffff; i++) {
			Context context = ContextDelegator.getContext();
			context.init(1);

			Logger.debug("-----Started ----- W Transaction "+context.getContextId()+" on "+ClustersManager.baseClusterId);
			try {
				transfer(accountNum1, accountNum2, transfer, context);
			} catch (Exception e) {
				e.printStackTrace();
			}

			try {
				if (TMProxy.getTMProxy().requestCommit(context.getContextId())) {
					return;
				}
			} catch (Exception e) {				e.printStackTrace();
			}
			
//			try {
//				Thread.sleep((ClustersManager.baseClusterId+1)*100);
//			} catch (InterruptedException e) {
//				e.printStackTrace();
//			}
		}
		Logger.fetal("Unable to complete transaction in given number of tries");
	}

	public static void transfer(String accountNum1, String accountNum2,
			int amount, Context c) throws Exception {
		BankAccount account1 = (BankAccount) TMProxy.getTMProxy().write(
				accountNum1, c.getContextId());
		BankAccount account2 = (BankAccount) TMProxy.getTMProxy().write(
				accountNum2, c.getContextId());

		Logger.debug("Read value ac1="+account1.amount + " ac2="+account2.amount);
		for (int i = 0; i < Benchmark.calls; i++)
			account1.withdraw(amount);

		for (int i = 0; i < Benchmark.calls; i++)
			account2.deposit(amount);

		Logger.debug("Written value ac1="+account1.amount + " ac2="+account2.amount);
	}

	@Override
	public String toString() {
		return getId() + "---" + hashCode();
	}

	@Override
	public QObject clone() {
		BankAccount bac = new BankAccount();
		bac.amount = amount;
		bac.id = id;
		return bac;
	}
}